summaryrefslogtreecommitdiff
path: root/app/[lng]
diff options
context:
space:
mode:
Diffstat (limited to 'app/[lng]')
-rw-r--r--app/[lng]/evcp/(evcp)/basic-contract-template/page.tsx74
-rw-r--r--app/[lng]/evcp/(evcp)/basic-contract/page.tsx74
-rw-r--r--app/[lng]/evcp/(evcp)/bid-projects/page.tsx74
-rw-r--r--app/[lng]/evcp/(evcp)/bqcbe/page.tsx74
-rw-r--r--app/[lng]/evcp/(evcp)/bqtbe/page.tsx2
-rw-r--r--app/[lng]/evcp/(evcp)/budgetary-rfq/[id]/cbe/page.tsx2
-rw-r--r--app/[lng]/evcp/(evcp)/budgetary-rfq/[id]/layout.tsx30
-rw-r--r--app/[lng]/evcp/(evcp)/budgetary-rfq/[id]/page.tsx2
-rw-r--r--app/[lng]/evcp/(evcp)/budgetary-rfq/[id]/tbe/page.tsx2
-rw-r--r--app/[lng]/evcp/(evcp)/budgetary/[id]/cbe/page.tsx2
-rw-r--r--app/[lng]/evcp/(evcp)/budgetary/[id]/layout.tsx42
-rw-r--r--app/[lng]/evcp/(evcp)/budgetary/[id]/page.tsx2
-rw-r--r--app/[lng]/evcp/(evcp)/budgetary/[id]/tbe/page.tsx2
-rw-r--r--app/[lng]/evcp/(evcp)/dashboard/page.tsx17
-rw-r--r--app/[lng]/evcp/(evcp)/equip-class/page.tsx4
-rw-r--r--app/[lng]/evcp/(evcp)/form-list/page.tsx4
-rw-r--r--app/[lng]/evcp/(evcp)/po/page.tsx2
-rw-r--r--app/[lng]/evcp/(evcp)/pq-criteria/[id]/page.tsx2
-rw-r--r--app/[lng]/evcp/(evcp)/pq-criteria/page.tsx2
-rw-r--r--app/[lng]/evcp/(evcp)/pq/[vendorId]/page.tsx5
-rw-r--r--app/[lng]/evcp/(evcp)/project-vendors/page.tsx74
-rw-r--r--app/[lng]/evcp/(evcp)/report/page.tsx53
-rw-r--r--app/[lng]/evcp/(evcp)/rfq/[id]/cbe/page.tsx26
-rw-r--r--app/[lng]/evcp/(evcp)/rfq/[id]/layout.tsx33
-rw-r--r--app/[lng]/evcp/(evcp)/rfq/[id]/page.tsx2
-rw-r--r--app/[lng]/evcp/(evcp)/rfq/[id]/tbe/page.tsx2
-rw-r--r--app/[lng]/evcp/(evcp)/tag-numbering/page.tsx2
-rw-r--r--app/[lng]/evcp/(evcp)/tasks/page.tsx4
-rw-r--r--app/[lng]/evcp/(evcp)/tbe/page.tsx4
-rw-r--r--app/[lng]/evcp/(evcp)/vendor-candidates/page.tsx30
-rw-r--r--app/[lng]/evcp/(evcp)/vendor-type/page.tsx70
-rw-r--r--app/[lng]/evcp/(evcp)/vendors/[id]/info/layout.tsx16
-rw-r--r--app/[lng]/evcp/(evcp)/vendors/[id]/info/rfq-history/page.tsx2
-rw-r--r--app/[lng]/evcp/(evcp)/vendors/page.tsx6
-rw-r--r--app/[lng]/partners/(partners)/basic-contract/page.tsx77
-rw-r--r--app/[lng]/partners/(partners)/cbe/page.tsx86
-rw-r--r--app/[lng]/partners/(partners)/dashboard/page.tsx53
-rw-r--r--app/[lng]/partners/(partners)/document-list/layout.tsx8
-rw-r--r--app/[lng]/partners/(partners)/documents/layout.tsx8
-rw-r--r--app/[lng]/partners/(partners)/report/page.tsx17
-rw-r--r--app/[lng]/partners/(partners)/vendor-data/form/[packageId]/[formId]/page.tsx41
-rw-r--r--app/[lng]/partners/(partners)/vendor-data/layout.tsx8
-rw-r--r--app/[lng]/partners/(partners)/vendor-data/page.tsx2
-rw-r--r--app/[lng]/partners/pq/page.tsx26
44 files changed, 923 insertions, 145 deletions
diff --git a/app/[lng]/evcp/(evcp)/basic-contract-template/page.tsx b/app/[lng]/evcp/(evcp)/basic-contract-template/page.tsx
new file mode 100644
index 00000000..adc57ed9
--- /dev/null
+++ b/app/[lng]/evcp/(evcp)/basic-contract-template/page.tsx
@@ -0,0 +1,74 @@
+import * as React from "react"
+import { type SearchParams } from "@/types/table"
+
+import { getValidFilters } from "@/lib/data-table"
+import { Skeleton } from "@/components/ui/skeleton"
+import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton"
+import { Shell } from "@/components/shell"
+import { getBasicContractTemplates } from "@/lib/basic-contract/service"
+import { searchParamsTemplatesCache } from "@/lib/basic-contract/validations"
+import { BasicContractTemplateTable } from "@/lib/basic-contract/template/basic-contract-template"
+
+
+interface IndexPageProps {
+ searchParams: Promise<SearchParams>
+}
+
+export default async function IndexPage(props: IndexPageProps) {
+ const searchParams = await props.searchParams
+ const search = searchParamsTemplatesCache.parse(searchParams)
+
+ const validFilters = getValidFilters(search.filters)
+
+ const promises = Promise.all([
+ getBasicContractTemplates({
+ ...search,
+ filters: validFilters,
+ }),
+
+ ])
+
+ return (
+ <Shell className="gap-2">
+ <div className="flex items-center justify-between space-y-2">
+ <div className="flex items-center justify-between space-y-2">
+ <div>
+ <h2 className="text-2xl font-bold tracking-tight">
+ 기본계약서 템플릿 관리
+ </h2>
+ <p className="text-muted-foreground">
+ 기본계약서를 비롯하여 초기 서명이 필요한 문서를 등록하고 편집할 수 있습니다. 활성화된 템플릿이 서명 요청의 리스트에 나타나게 됩니다..{" "}
+ {/* <span className="inline-flex items-center whitespace-nowrap">
+ <Ellipsis className="size-3" />
+ <span className="ml-1">버튼</span>
+ </span>
+ 을 통해 담당자 연락처, 입찰 이력, 계약 이력, 패키지 내용 등을 확인 할 수 있습니다. */}
+ </p>
+ </div>
+ </div>
+ </div>
+
+ <React.Suspense fallback={<Skeleton className="h-7 w-52" />}>
+ {/* <DateRangePicker
+ triggerSize="sm"
+ triggerClassName="ml-auto w-56 sm:w-60"
+ align="end"
+ shallow={false}
+ /> */}
+ </React.Suspense>
+ <React.Suspense
+ fallback={
+ <DataTableSkeleton
+ columnCount={6}
+ searchableColumnCount={1}
+ filterableColumnCount={2}
+ cellWidths={["10rem", "40rem", "12rem", "12rem", "8rem", "8rem"]}
+ shrinkZero
+ />
+ }
+ >
+ <BasicContractTemplateTable promises={promises} />
+ </React.Suspense>
+ </Shell>
+ )
+}
diff --git a/app/[lng]/evcp/(evcp)/basic-contract/page.tsx b/app/[lng]/evcp/(evcp)/basic-contract/page.tsx
new file mode 100644
index 00000000..a043e530
--- /dev/null
+++ b/app/[lng]/evcp/(evcp)/basic-contract/page.tsx
@@ -0,0 +1,74 @@
+import * as React from "react"
+import { type SearchParams } from "@/types/table"
+
+import { getValidFilters } from "@/lib/data-table"
+import { Skeleton } from "@/components/ui/skeleton"
+import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton"
+import { Shell } from "@/components/shell"
+import { getBasicContracts } from "@/lib/basic-contract/service"
+import { searchParamsCache } from "@/lib/basic-contract/validations"
+import { BasicContractsTable } from "@/lib/basic-contract/status/basic-contract-table"
+
+
+interface IndexPageProps {
+ searchParams: Promise<SearchParams>
+}
+
+export default async function IndexPage(props: IndexPageProps) {
+ const searchParams = await props.searchParams
+ const search = searchParamsCache.parse(searchParams)
+
+ const validFilters = getValidFilters(search.filters)
+
+ const promises = Promise.all([
+ getBasicContracts({
+ ...search,
+ filters: validFilters,
+ }),
+
+ ])
+
+ return (
+ <Shell className="gap-2">
+ <div className="flex items-center justify-between space-y-2">
+ <div className="flex items-center justify-between space-y-2">
+ <div>
+ <h2 className="text-2xl font-bold tracking-tight">
+ 기본계약서 서명 현황
+ </h2>
+ <p className="text-muted-foreground">
+ 기본계약서를 비롯하여 초기 서명이 필요한 문서의 서명 현황을 확인할 수 있고 서명된 문서들을 다운로드할 수 있습니다. {" "}
+ {/* <span className="inline-flex items-center whitespace-nowrap">
+ <Ellipsis className="size-3" />
+ <span className="ml-1">버튼</span>
+ </span>
+ 을 통해 담당자 연락처, 입찰 이력, 계약 이력, 패키지 내용 등을 확인 할 수 있습니다. */}
+ </p>
+ </div>
+ </div>
+ </div>
+
+ <React.Suspense fallback={<Skeleton className="h-7 w-52" />}>
+ {/* <DateRangePicker
+ triggerSize="sm"
+ triggerClassName="ml-auto w-56 sm:w-60"
+ align="end"
+ shallow={false}
+ /> */}
+ </React.Suspense>
+ <React.Suspense
+ fallback={
+ <DataTableSkeleton
+ columnCount={6}
+ searchableColumnCount={1}
+ filterableColumnCount={2}
+ cellWidths={["10rem", "40rem", "12rem", "12rem", "8rem", "8rem"]}
+ shrinkZero
+ />
+ }
+ >
+ <BasicContractsTable promises={promises} />
+ </React.Suspense>
+ </Shell>
+ )
+}
diff --git a/app/[lng]/evcp/(evcp)/bid-projects/page.tsx b/app/[lng]/evcp/(evcp)/bid-projects/page.tsx
new file mode 100644
index 00000000..3390f4f3
--- /dev/null
+++ b/app/[lng]/evcp/(evcp)/bid-projects/page.tsx
@@ -0,0 +1,74 @@
+import * as React from "react"
+import { type SearchParams } from "@/types/table"
+
+import { getValidFilters } from "@/lib/data-table"
+import { Skeleton } from "@/components/ui/skeleton"
+import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton"
+import { Shell } from "@/components/shell"
+import { getBidProjectLists } from "@/lib/bidding-projects/service"
+import { searchParamsBidProjectsCache } from "@/lib/bidding-projects/validation"
+import { BidProjectsTable } from "@/lib/bidding-projects/table/projects-table"
+
+
+interface IndexPageProps {
+ searchParams: Promise<SearchParams>
+}
+
+export default async function IndexPage(props: IndexPageProps) {
+ const searchParams = await props.searchParams
+ const search = searchParamsBidProjectsCache.parse(searchParams)
+
+ const validFilters = getValidFilters(search.filters)
+
+ const promises = Promise.all([
+ getBidProjectLists({
+ ...search,
+ filters: validFilters,
+ }),
+
+ ])
+
+ return (
+ <Shell className="gap-2">
+ <div className="flex items-center justify-between space-y-2">
+ <div className="flex items-center justify-between space-y-2">
+ <div>
+ <h2 className="text-2xl font-bold tracking-tight">
+ 견적 프로젝트 리스트
+ </h2>
+ <p className="text-muted-foreground">
+ SAP로부터 수신할 수 있습니다.{" "}
+ {/* <span className="inline-flex items-center whitespace-nowrap">
+ <Ellipsis className="size-3" />
+ <span className="ml-1">버튼</span>
+ </span>
+ 을 통해 담당자 연락처, 입찰 이력, 계약 이력, 패키지 내용 등을 확인 할 수 있습니다. */}
+ </p>
+ </div>
+ </div>
+ </div>
+
+ <React.Suspense fallback={<Skeleton className="h-7 w-52" />}>
+ {/* <DateRangePicker
+ triggerSize="sm"
+ triggerClassName="ml-auto w-56 sm:w-60"
+ align="end"
+ shallow={false}
+ /> */}
+ </React.Suspense>
+ <React.Suspense
+ fallback={
+ <DataTableSkeleton
+ columnCount={6}
+ searchableColumnCount={1}
+ filterableColumnCount={2}
+ cellWidths={["10rem", "40rem", "12rem", "12rem", "8rem", "8rem"]}
+ shrinkZero
+ />
+ }
+ >
+ <BidProjectsTable promises={promises} />
+ </React.Suspense>
+ </Shell>
+ )
+}
diff --git a/app/[lng]/evcp/(evcp)/bqcbe/page.tsx b/app/[lng]/evcp/(evcp)/bqcbe/page.tsx
new file mode 100644
index 00000000..ae503feb
--- /dev/null
+++ b/app/[lng]/evcp/(evcp)/bqcbe/page.tsx
@@ -0,0 +1,74 @@
+import { type SearchParams } from "@/types/table"
+import { getValidFilters } from "@/lib/data-table"
+import { getAllCBE } from "@/lib/rfqs/service"
+import { searchParamsCBECache } from "@/lib/rfqs/validations"
+
+import { AllCbeTable } from "@/lib/cbe/table/cbe-table"
+
+import { RfqType } from "@/lib/rfqs/validations"
+import * as React from "react"
+import { Shell } from "@/components/shell"
+import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton"
+
+interface IndexPageProps {
+ // Next.js 13 App Router에서 기본으로 주어지는 객체들
+ params: {
+ lng: string
+ }
+ searchParams: Promise<SearchParams>
+ rfqType: RfqType
+}
+
+export default async function RfqCBEPage(props: IndexPageProps) {
+ const resolvedParams = await props.params
+ const lng = resolvedParams.lng
+
+ const rfqType = props.rfqType ?? RfqType.BUDGETARY // rfqType이 없으면 BUDGETARY로 설정
+
+ // 2) SearchParams 파싱 (Zod)
+ // - "filters", "page", "perPage", "sort" 등 contact 전용 컬럼
+ const searchParams = await props.searchParams
+ const search = searchParamsCBECache.parse(searchParams)
+ const validFilters = getValidFilters(search.filters)
+
+ const promises = Promise.all([
+ getAllCBE({
+ ...search,
+ filters: validFilters,
+ rfqType
+ }
+ )
+ ])
+
+ // 4) 렌더링
+ return (
+ <Shell className="gap-2">
+ <div className="flex items-center justify-between space-y-2">
+ <div className="flex items-center justify-between space-y-2">
+ <div>
+ <h2 className="text-2xl font-bold tracking-tight">
+ Commercial Bid Evaluation
+ </h2>
+ <p className="text-muted-foreground">
+ 초대된 협력업체에게 CBE를 보낼 수 있습니다. <br/>체크박스 선택을 하면 초대 버튼이 활성화됩니다. 버튼 클릭 후 첨부파일을 함께 전송하면 CBE 내용이 메일로 전달되고 eVCP에도 협력업체가 입력할 수 있게 자동 생성됩니다.
+ </p>
+ </div>
+ </div>
+ </div>
+
+ <React.Suspense
+ fallback={
+ <DataTableSkeleton
+ columnCount={6}
+ searchableColumnCount={1}
+ filterableColumnCount={2}
+ cellWidths={["10rem", "40rem", "12rem", "12rem", "8rem", "8rem"]}
+ shrinkZero
+ />
+ }
+ >
+ <AllCbeTable promises={promises}/>
+ </React.Suspense>
+ </Shell>
+ )
+} \ No newline at end of file
diff --git a/app/[lng]/evcp/(evcp)/bqtbe/page.tsx b/app/[lng]/evcp/(evcp)/bqtbe/page.tsx
index 655bd30a..4989c235 100644
--- a/app/[lng]/evcp/(evcp)/bqtbe/page.tsx
+++ b/app/[lng]/evcp/(evcp)/bqtbe/page.tsx
@@ -48,7 +48,7 @@ export default async function RfqTBEPage(props: IndexPageProps) {
Technical Bid Evaluation
</h2>
<p className="text-muted-foreground">
- 초대된 벤더에게 TBE를 보낼 수 있습니다. <br/>체크박스 선택을 하면 초대 버튼이 활성화됩니다. 버튼 클릭 후 첨부파일을 함께 전송하면 TBE 내용이 메일로 전달되고 eVCP에도 벤더가 입력할 수 있게 자동 생성됩니다.
+ 초대된 협력업체에게 TBE를 보낼 수 있습니다. <br/>체크박스 선택을 하면 초대 버튼이 활성화됩니다. 버튼 클릭 후 첨부파일을 함께 전송하면 TBE 내용이 메일로 전달되고 eVCP에도 협력업체가 입력할 수 있게 자동 생성됩니다.
</p>
</div>
</div>
diff --git a/app/[lng]/evcp/(evcp)/budgetary-rfq/[id]/cbe/page.tsx b/app/[lng]/evcp/(evcp)/budgetary-rfq/[id]/cbe/page.tsx
index 9a4ae7eb..956facd3 100644
--- a/app/[lng]/evcp/(evcp)/budgetary-rfq/[id]/cbe/page.tsx
+++ b/app/[lng]/evcp/(evcp)/budgetary-rfq/[id]/cbe/page.tsx
@@ -44,7 +44,7 @@ export default async function RfqTBEPage(props: IndexPageProps) {
Commercial Bid Evaluation
</h3>
<p className="text-sm text-muted-foreground">
- 초대된 벤더에게 CBE를 보낼 수 있습니다. <br/>체크박스 선택을 하면 초대 버튼이 활성화됩니다. 버튼 클릭 후 첨부파일을 함께 전송하면 TBE 내용이 메일로 전달되고 eVCP에도 벤더가 입력할 수 있게 자동 생성됩니다.
+ 초대된 협력업체에게 CBE를 보낼 수 있습니다. <br/>체크박스 선택을 하면 초대 버튼이 활성화됩니다. 버튼 클릭 후 첨부파일을 함께 전송하면 CBE 내용이 메일로 전달되고 eVCP에도 협력업체가 입력할 수 있게 자동 생성됩니다.
</p>
</div>
<Separator />
diff --git a/app/[lng]/evcp/(evcp)/budgetary-rfq/[id]/layout.tsx b/app/[lng]/evcp/(evcp)/budgetary-rfq/[id]/layout.tsx
index 39f045e5..ba7c071c 100644
--- a/app/[lng]/evcp/(evcp)/budgetary-rfq/[id]/layout.tsx
+++ b/app/[lng]/evcp/(evcp)/budgetary-rfq/[id]/layout.tsx
@@ -1,11 +1,13 @@
import { Metadata } from "next"
+import Link from "next/link"
+import { ArrowLeft } from "lucide-react"
import { Separator } from "@/components/ui/separator"
import { SidebarNav } from "@/components/layout/sidebar-nav"
-import { findVendorById } from "@/lib/vendors/service" // 가정: 여기에 findVendorById가 있다고 가정
-import { Rfq, RfqWithItems } from "@/db/schema/rfq"
+import { RfqViewWithItems } from "@/db/schema/rfq"
import { findRfqById } from "@/lib/rfqs/service"
import { formatDate } from "@/lib/utils"
+import { Button } from "@/components/ui/button"
export const metadata: Metadata = {
title: "Vendor Detail",
@@ -25,8 +27,8 @@ export default async function RfqLayout({
const id = resolvedParams.id
const idAsNumber = Number(id)
- // 2) DB에서 해당 벤더 정보 조회
- const rfq: RfqWithItems | null = await findRfqById(idAsNumber)
+ // 2) DB에서 해당 협력업체 정보 조회
+ const rfq: RfqViewWithItems | null = await findRfqById(idAsNumber)
// 3) 사이드바 메뉴
const sidebarNavItems = [
@@ -50,27 +52,35 @@ export default async function RfqLayout({
<div className="container py-6">
<section className="overflow-hidden rounded-[0.5rem] border bg-background shadow">
<div className="hidden space-y-6 p-10 pb-16 md:block">
+ <div className="flex items-center justify-end mb-4">
+ <Link href={`/${lng}/evcp/budgetary-rfq`} passHref>
+ <Button variant="ghost" className="flex items-center text-primary hover:text-primary/80 transition-colors p-0 h-auto">
+ <ArrowLeft className="mr-1 h-4 w-4" />
+ <span>Budgetary RFQ 목록으로 돌아가기</span>
+ </Button>
+ </Link>
+ </div>
<div className="space-y-0.5">
- {/* 4) 벤더 정보가 있으면 코드 + 이름 + "상세 정보" 표기 */}
+ {/* 4) 협력업체 정보가 있으면 코드 + 이름 + "상세 정보" 표기 */}
<h2 className="text-2xl font-bold tracking-tight">
{rfq
- ? `${rfq.rfqCode ?? ""} 관리`
+ ? `${rfq.projectCode ?? ""} ${rfq.rfqCode ?? ""} 관리`
: "Loading RFQ..."}
</h2>
-
+
<p className="text-muted-foreground">
{rfq
? `${rfq.description ?? ""} ${rfq.lines.map(line => line.itemCode).join(", ")}`
: ""}
</p>
- <h3>Due Date:{ rfq && <strong>{formatDate(rfq?.dueDate)}</strong>}</h3>
+ <h3>Due Date:{rfq && rfq?.dueDate && <strong>{formatDate(rfq?.dueDate)}</strong>}</h3>
</div>
<Separator className="my-6" />
<div className="flex flex-col space-y-8 lg:flex-row lg:space-x-12 lg:space-y-0">
- <aside className="-mx-4 lg:w-1/6">
+ <aside className="lg:w-64 flex-shrink-0">
<SidebarNav items={sidebarNavItems} />
</aside>
- <div className="flex-1">{children}</div>
+ <div className="lg:w-[calc(100%-16rem)] overflow-auto">{children}</div>
</div>
</div>
</section>
diff --git a/app/[lng]/evcp/(evcp)/budgetary-rfq/[id]/page.tsx b/app/[lng]/evcp/(evcp)/budgetary-rfq/[id]/page.tsx
index f6160574..dd9df563 100644
--- a/app/[lng]/evcp/(evcp)/budgetary-rfq/[id]/page.tsx
+++ b/app/[lng]/evcp/(evcp)/budgetary-rfq/[id]/page.tsx
@@ -45,7 +45,7 @@ export default async function RfqPage(props: IndexPageProps) {
Vendors
</h3>
<p className="text-sm text-muted-foreground">
- 등록된 벤더 중에서 이 RFQ 아이템에 매칭되는 업체를 보여줍니다. <br/>"발행하기" 버튼을 통해 RFQ를 전송하면 첨부파일과 함께 RFQ 내용이 메일로 전달되고 eVCP에도 벤더가 입력할 수 있게 자동 생성됩니다.
+ 등록된 협력업체 중에서 이 RFQ 아이템에 매칭되는 업체를 보여줍니다. <br/>"발행하기" 버튼을 통해 RFQ를 전송하면 첨부파일과 함께 RFQ 내용이 메일로 전달되고 eVCP에도 협력업체가 입력할 수 있게 자동 생성됩니다.
</p>
</div>
<Separator />
diff --git a/app/[lng]/evcp/(evcp)/budgetary-rfq/[id]/tbe/page.tsx b/app/[lng]/evcp/(evcp)/budgetary-rfq/[id]/tbe/page.tsx
index a6259696..ec894e1c 100644
--- a/app/[lng]/evcp/(evcp)/budgetary-rfq/[id]/tbe/page.tsx
+++ b/app/[lng]/evcp/(evcp)/budgetary-rfq/[id]/tbe/page.tsx
@@ -43,7 +43,7 @@ export default async function RfqTBEPage(props: IndexPageProps) {
Technical Bid Evaluation
</h3>
<p className="text-sm text-muted-foreground">
- 초대된 벤더에게 TBE를 보낼 수 있습니다. <br/>체크박스 선택을 하면 초대 버튼이 활성화됩니다. 버튼 클릭 후 첨부파일을 함께 전송하면 TBE 내용이 메일로 전달되고 eVCP에도 벤더가 입력할 수 있게 자동 생성됩니다.
+ 초대된 협력업체에게 TBE를 보낼 수 있습니다. <br/>체크박스 선택을 하면 초대 버튼이 활성화됩니다. 버튼 클릭 후 첨부파일을 함께 전송하면 TBE 내용이 메일로 전달되고 eVCP에도 협력업체가 입력할 수 있게 자동 생성됩니다.
</p>
</div>
<Separator />
diff --git a/app/[lng]/evcp/(evcp)/budgetary/[id]/cbe/page.tsx b/app/[lng]/evcp/(evcp)/budgetary/[id]/cbe/page.tsx
index 9a4ae7eb..956facd3 100644
--- a/app/[lng]/evcp/(evcp)/budgetary/[id]/cbe/page.tsx
+++ b/app/[lng]/evcp/(evcp)/budgetary/[id]/cbe/page.tsx
@@ -44,7 +44,7 @@ export default async function RfqTBEPage(props: IndexPageProps) {
Commercial Bid Evaluation
</h3>
<p className="text-sm text-muted-foreground">
- 초대된 벤더에게 CBE를 보낼 수 있습니다. <br/>체크박스 선택을 하면 초대 버튼이 활성화됩니다. 버튼 클릭 후 첨부파일을 함께 전송하면 TBE 내용이 메일로 전달되고 eVCP에도 벤더가 입력할 수 있게 자동 생성됩니다.
+ 초대된 협력업체에게 CBE를 보낼 수 있습니다. <br/>체크박스 선택을 하면 초대 버튼이 활성화됩니다. 버튼 클릭 후 첨부파일을 함께 전송하면 CBE 내용이 메일로 전달되고 eVCP에도 협력업체가 입력할 수 있게 자동 생성됩니다.
</p>
</div>
<Separator />
diff --git a/app/[lng]/evcp/(evcp)/budgetary/[id]/layout.tsx b/app/[lng]/evcp/(evcp)/budgetary/[id]/layout.tsx
index 39f045e5..b0711c66 100644
--- a/app/[lng]/evcp/(evcp)/budgetary/[id]/layout.tsx
+++ b/app/[lng]/evcp/(evcp)/budgetary/[id]/layout.tsx
@@ -1,11 +1,12 @@
import { Metadata } from "next"
-
+import Link from "next/link"
+import { ArrowLeft } from "lucide-react"
import { Separator } from "@/components/ui/separator"
import { SidebarNav } from "@/components/layout/sidebar-nav"
-import { findVendorById } from "@/lib/vendors/service" // 가정: 여기에 findVendorById가 있다고 가정
-import { Rfq, RfqWithItems } from "@/db/schema/rfq"
+import { RfqViewWithItems } from "@/db/schema/rfq"
import { findRfqById } from "@/lib/rfqs/service"
import { formatDate } from "@/lib/utils"
+import { Button } from "@/components/ui/button"
export const metadata: Metadata = {
title: "Vendor Detail",
@@ -18,16 +19,16 @@ export default async function RfqLayout({
children: React.ReactNode
params: { lng: string, id: string }
}) {
-
+
// 1) URL 파라미터에서 id 추출, Number로 변환
const resolvedParams = await params
const lng = resolvedParams.lng
const id = resolvedParams.id
-
+
const idAsNumber = Number(id)
- // 2) DB에서 해당 벤더 정보 조회
- const rfq: RfqWithItems | null = await findRfqById(idAsNumber)
-
+ // 2) DB에서 해당 협력업체 정보 조회
+ const rfq: RfqViewWithItems | null = await findRfqById(idAsNumber)
+
// 3) 사이드바 메뉴
const sidebarNavItems = [
{
@@ -42,35 +43,44 @@ export default async function RfqLayout({
title: "CBE",
href: `/${lng}/evcp/budgetary/${id}/cbe`,
},
-
]
-
+
return (
<>
<div className="container py-6">
<section className="overflow-hidden rounded-[0.5rem] border bg-background shadow">
<div className="hidden space-y-6 p-10 pb-16 md:block">
+ {/* RFQ 목록으로 돌아가는 링크 추가 */}
+ <div className="flex items-center justify-end mb-4">
+ <Link href={`/${lng}/evcp/budgetary`} passHref>
+ <Button variant="ghost" className="flex items-center text-primary hover:text-primary/80 transition-colors p-0 h-auto">
+ <ArrowLeft className="mr-1 h-4 w-4" />
+ <span>Budgetary Quote 목록으로 돌아가기</span>
+ </Button>
+ </Link>
+ </div>
+
<div className="space-y-0.5">
- {/* 4) 벤더 정보가 있으면 코드 + 이름 + "상세 정보" 표기 */}
+ {/* 4) 협력업체 정보가 있으면 코드 + 이름 + "상세 정보" 표기 */}
<h2 className="text-2xl font-bold tracking-tight">
{rfq
- ? `${rfq.rfqCode ?? ""} 관리`
+ ? `${rfq.projectCode ?? ""} ${rfq.rfqCode ?? ""} 관리`
: "Loading RFQ..."}
</h2>
-
+
<p className="text-muted-foreground">
{rfq
? `${rfq.description ?? ""} ${rfq.lines.map(line => line.itemCode).join(", ")}`
: ""}
</p>
- <h3>Due Date:{ rfq && <strong>{formatDate(rfq?.dueDate)}</strong>}</h3>
+ <h3>Due Date:{rfq && rfq?.dueDate && <strong>{formatDate(rfq?.dueDate)}</strong>}</h3>
</div>
<Separator className="my-6" />
<div className="flex flex-col space-y-8 lg:flex-row lg:space-x-12 lg:space-y-0">
- <aside className="-mx-4 lg:w-1/6">
+ <aside className="lg:w-64 flex-shrink-0">
<SidebarNav items={sidebarNavItems} />
</aside>
- <div className="flex-1">{children}</div>
+ <div className="lg:w-[calc(100%-16rem)] overflow-auto">{children}</div>
</div>
</div>
</section>
diff --git a/app/[lng]/evcp/(evcp)/budgetary/[id]/page.tsx b/app/[lng]/evcp/(evcp)/budgetary/[id]/page.tsx
index f6160574..dd9df563 100644
--- a/app/[lng]/evcp/(evcp)/budgetary/[id]/page.tsx
+++ b/app/[lng]/evcp/(evcp)/budgetary/[id]/page.tsx
@@ -45,7 +45,7 @@ export default async function RfqPage(props: IndexPageProps) {
Vendors
</h3>
<p className="text-sm text-muted-foreground">
- 등록된 벤더 중에서 이 RFQ 아이템에 매칭되는 업체를 보여줍니다. <br/>"발행하기" 버튼을 통해 RFQ를 전송하면 첨부파일과 함께 RFQ 내용이 메일로 전달되고 eVCP에도 벤더가 입력할 수 있게 자동 생성됩니다.
+ 등록된 협력업체 중에서 이 RFQ 아이템에 매칭되는 업체를 보여줍니다. <br/>"발행하기" 버튼을 통해 RFQ를 전송하면 첨부파일과 함께 RFQ 내용이 메일로 전달되고 eVCP에도 협력업체가 입력할 수 있게 자동 생성됩니다.
</p>
</div>
<Separator />
diff --git a/app/[lng]/evcp/(evcp)/budgetary/[id]/tbe/page.tsx b/app/[lng]/evcp/(evcp)/budgetary/[id]/tbe/page.tsx
index a6259696..ec894e1c 100644
--- a/app/[lng]/evcp/(evcp)/budgetary/[id]/tbe/page.tsx
+++ b/app/[lng]/evcp/(evcp)/budgetary/[id]/tbe/page.tsx
@@ -43,7 +43,7 @@ export default async function RfqTBEPage(props: IndexPageProps) {
Technical Bid Evaluation
</h3>
<p className="text-sm text-muted-foreground">
- 초대된 벤더에게 TBE를 보낼 수 있습니다. <br/>체크박스 선택을 하면 초대 버튼이 활성화됩니다. 버튼 클릭 후 첨부파일을 함께 전송하면 TBE 내용이 메일로 전달되고 eVCP에도 벤더가 입력할 수 있게 자동 생성됩니다.
+ 초대된 협력업체에게 TBE를 보낼 수 있습니다. <br/>체크박스 선택을 하면 초대 버튼이 활성화됩니다. 버튼 클릭 후 첨부파일을 함께 전송하면 TBE 내용이 메일로 전달되고 eVCP에도 협력업체가 입력할 수 있게 자동 생성됩니다.
</p>
</div>
<Separator />
diff --git a/app/[lng]/evcp/(evcp)/dashboard/page.tsx b/app/[lng]/evcp/(evcp)/dashboard/page.tsx
new file mode 100644
index 00000000..1d61dc16
--- /dev/null
+++ b/app/[lng]/evcp/(evcp)/dashboard/page.tsx
@@ -0,0 +1,17 @@
+// app/invalid-access/page.tsx
+
+export default function InvalidAccessPage() {
+ return (
+ <main style={{ padding: '40px', textAlign: 'center' }}>
+ <h1>부적절한 접근입니다</h1>
+ <p>
+ 협력업체(Vendor)가 EVCP 화면에 접속하거나 <br />
+ SHI 계정이 협력업체 화면에 접속하려고 시도하는 경우입니다.
+ </p>
+ <p>
+ <strong>접근 권한이 없으므로, 다른 화면으로 이동해 주세요.</strong>
+ </p>
+ </main>
+ );
+ }
+ \ No newline at end of file
diff --git a/app/[lng]/evcp/(evcp)/equip-class/page.tsx b/app/[lng]/evcp/(evcp)/equip-class/page.tsx
index 375eb69e..cfa8f133 100644
--- a/app/[lng]/evcp/(evcp)/equip-class/page.tsx
+++ b/app/[lng]/evcp/(evcp)/equip-class/page.tsx
@@ -35,10 +35,10 @@ export default async function IndexPage(props: IndexPageProps) {
<div className="flex items-center justify-between space-y-2">
<div>
<h2 className="text-2xl font-bold tracking-tight">
- Object Class List from S-EDP
+ 객체 클래스 목록 from S-EDP
</h2>
<p className="text-muted-foreground">
- Object Class List를 확인할 수 있습니다.{" "}
+ 객체 클래스 목록을 확인할 수 있습니다.{" "}
{/* <span className="inline-flex items-center whitespace-nowrap">
<Ellipsis className="size-3" />
<span className="ml-1">버튼</span>
diff --git a/app/[lng]/evcp/(evcp)/form-list/page.tsx b/app/[lng]/evcp/(evcp)/form-list/page.tsx
index f96917d6..a6cf7d9e 100644
--- a/app/[lng]/evcp/(evcp)/form-list/page.tsx
+++ b/app/[lng]/evcp/(evcp)/form-list/page.tsx
@@ -35,10 +35,10 @@ export default async function IndexPage(props: IndexPageProps) {
<div className="flex items-center justify-between space-y-2">
<div>
<h2 className="text-2xl font-bold tracking-tight">
- Form List from S-EDP
+ 레지스터 목록 from S-EDP
</h2>
<p className="text-muted-foreground">
- 벤더 데이터 입력을 위한 Form 리스트입니다.{" "}
+ 협력업체 데이터 입력을 위한 레지스터 목록 리스트입니다.{" "}
{/* <span className="inline-flex items-center whitespace-nowrap">
<Ellipsis className="size-3" />
<span className="ml-1">버튼</span>
diff --git a/app/[lng]/evcp/(evcp)/po/page.tsx b/app/[lng]/evcp/(evcp)/po/page.tsx
index fa528df0..7868e231 100644
--- a/app/[lng]/evcp/(evcp)/po/page.tsx
+++ b/app/[lng]/evcp/(evcp)/po/page.tsx
@@ -37,7 +37,7 @@ export default async function IndexPage(props: IndexPageProps) {
PO 확인 및 전자서명
</h2>
<p className="text-muted-foreground">
- 기간계 시스템으로부터 PO를 확인하고 벤더에게 전자서명을 요청할 수 있습니다. 요쳥된 전자서명의 이력 또한 확인할 수 있습니다.
+ 기간계 시스템으로부터 PO를 확인하고 협력업체에게 전자서명을 요청할 수 있습니다. 요쳥된 전자서명의 이력 또한 확인할 수 있습니다.
</p>
</div>
diff --git a/app/[lng]/evcp/(evcp)/pq-criteria/[id]/page.tsx b/app/[lng]/evcp/(evcp)/pq-criteria/[id]/page.tsx
index f040a0ca..55b1e9df 100644
--- a/app/[lng]/evcp/(evcp)/pq-criteria/[id]/page.tsx
+++ b/app/[lng]/evcp/(evcp)/pq-criteria/[id]/page.tsx
@@ -48,7 +48,7 @@ export default async function ProjectPage(props: ProjectPageProps) {
Pre-Qualification Check Sheet
</h2>
<p className="text-muted-foreground">
- 벤더 등록을 위한, 벤더가 제출할 PQ 항목을: 프로젝트별로 관리할 수 있습니다.
+ 협력업체 등록을 위한, 협력업체가 제출할 PQ 항목을: 프로젝트별로 관리할 수 있습니다.
</p>
</div>
<ProjectSelectorWrapper selectedProjectId={projectId} />
diff --git a/app/[lng]/evcp/(evcp)/pq-criteria/page.tsx b/app/[lng]/evcp/(evcp)/pq-criteria/page.tsx
index 778baa93..7785b541 100644
--- a/app/[lng]/evcp/(evcp)/pq-criteria/page.tsx
+++ b/app/[lng]/evcp/(evcp)/pq-criteria/page.tsx
@@ -37,7 +37,7 @@ export default async function IndexPage(props: IndexPageProps) {
Pre-Qualification Check Sheet
</h2>
<p className="text-muted-foreground">
- 벤더 등록을 위한, 벤더가 제출할 PQ 항목을 관리할 수 있습니다.
+ 협력업체 등록을 위한, 협력업체가 제출할 PQ 항목을 관리할 수 있습니다.
</p>
</div>
<ProjectSelectorWrapper />
diff --git a/app/[lng]/evcp/(evcp)/pq/[vendorId]/page.tsx b/app/[lng]/evcp/(evcp)/pq/[vendorId]/page.tsx
index 4c2555a3..76bcfe59 100644
--- a/app/[lng]/evcp/(evcp)/pq/[vendorId]/page.tsx
+++ b/app/[lng]/evcp/(evcp)/pq/[vendorId]/page.tsx
@@ -1,7 +1,7 @@
import * as React from "react"
import { Shell } from "@/components/shell"
import { type SearchParams } from "@/types/table"
-import { getPQDataByVendorId, getVendorPQsList, loadGeneralPQData, loadProjectPQData } from "@/lib/pq/service"
+import { getPQDataByVendorId, getVendorPQsList, loadGeneralPQData, loadProjectPQAction, loadProjectPQData } from "@/lib/pq/service"
import { Vendor } from "@/db/schema/vendors"
import { findVendorById } from "@/lib/vendors/service"
import VendorPQAdminReview from "@/components/pq/pq-review-detail"
@@ -92,7 +92,8 @@ export default async function PQReviewPage(props: IndexPageProps) {
projectId={project.projectId}
projectName={project.projectName}
projectStatus={project.status}
- loadData={(vendorId, _projectId) => loadProjectPQData(vendorId, project.projectId)} pqType="project"
+ loadData={loadProjectPQAction}
+ pqType="project"
/>
</TabsContent>
))}
diff --git a/app/[lng]/evcp/(evcp)/project-vendors/page.tsx b/app/[lng]/evcp/(evcp)/project-vendors/page.tsx
new file mode 100644
index 00000000..dcc66071
--- /dev/null
+++ b/app/[lng]/evcp/(evcp)/project-vendors/page.tsx
@@ -0,0 +1,74 @@
+import * as React from "react"
+import { type SearchParams } from "@/types/table"
+
+import { getValidFilters } from "@/lib/data-table"
+import { Skeleton } from "@/components/ui/skeleton"
+import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton"
+import { Shell } from "@/components/shell"
+import { ProjectAVLTable } from "@/lib/project-avl/table/proejctAVL-table"
+import { getProjecTAVL } from "@/lib/project-avl/service"
+import { searchProjectAVLParamsCache } from "@/lib/project-avl/validations"
+
+
+interface IndexPageProps {
+ searchParams: Promise<SearchParams>
+}
+
+export default async function IndexPage(props: IndexPageProps) {
+ const searchParams = await props.searchParams
+ const search = searchProjectAVLParamsCache.parse(searchParams)
+
+ const validFilters = getValidFilters(search.filters)
+
+ const promises = Promise.all([
+ getProjecTAVL({
+ ...search,
+ filters: validFilters,
+ }),
+
+ ])
+
+ return (
+ <Shell className="gap-2">
+ <div className="flex items-center justify-between space-y-2">
+ <div className="flex items-center justify-between space-y-2">
+ <div>
+ <h2 className="text-2xl font-bold tracking-tight">
+ 프로젝트 AVL 리스트
+ </h2>
+ <p className="text-muted-foreground">
+ 프로젝트 PQ를 통과한 벤더의 리스트를 보여줍니다.{" "}
+ {/* <span className="inline-flex items-center whitespace-nowrap">
+ <Ellipsis className="size-3" />
+ <span className="ml-1">버튼</span>
+ </span>
+ 을 통해 담당자 연락처, 입찰 이력, 계약 이력, 패키지 내용 등을 확인 할 수 있습니다. */}
+ </p>
+ </div>
+ </div>
+ </div>
+
+ <React.Suspense fallback={<Skeleton className="h-7 w-52" />}>
+ {/* <DateRangePicker
+ triggerSize="sm"
+ triggerClassName="ml-auto w-56 sm:w-60"
+ align="end"
+ shallow={false}
+ /> */}
+ </React.Suspense>
+ <React.Suspense
+ fallback={
+ <DataTableSkeleton
+ columnCount={6}
+ searchableColumnCount={1}
+ filterableColumnCount={2}
+ cellWidths={["10rem", "40rem", "12rem", "12rem", "8rem", "8rem"]}
+ shrinkZero
+ />
+ }
+ >
+ <ProjectAVLTable promises={promises} />
+ </React.Suspense>
+ </Shell>
+ )
+}
diff --git a/app/[lng]/evcp/(evcp)/report/page.tsx b/app/[lng]/evcp/(evcp)/report/page.tsx
index a1e9f8be..3efaa7c3 100644
--- a/app/[lng]/evcp/(evcp)/report/page.tsx
+++ b/app/[lng]/evcp/(evcp)/report/page.tsx
@@ -1,8 +1,47 @@
+import * as React from "react"
+import { Skeleton } from "@/components/ui/skeleton"
+import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton"
+import { Shell } from "@/components/shell"
-export default function Pages() {
- return (
- <>
- test
- </>
- )
- } \ No newline at end of file
+
+
+export default async function IndexPage() {
+
+
+ return (
+ <Shell className="gap-2">
+ <div className="flex items-center justify-between">
+ <div>
+ <h2 className="text-2xl font-bold tracking-tight">
+ Dashboard
+ </h2>
+ <p className="text-muted-foreground">
+ 각종 지표 등을 대시보드로 표현하거나 리포트를 출력할 수 있습니다.
+ </p>
+ </div>
+ </div>
+
+ <React.Suspense fallback={<Skeleton className="h-7 w-52" />}>
+ {/* <DateRangePicker
+ triggerSize="sm"
+ triggerClassName="ml-auto w-56 sm:w-60"
+ align="end"
+ shallow={false}
+ /> */}
+ </React.Suspense>
+
+ <React.Suspense
+ fallback={
+ <DataTableSkeleton
+ columnCount={6}
+ searchableColumnCount={1}
+ filterableColumnCount={2}
+ cellWidths={["10rem", "40rem", "12rem", "12rem", "8rem", "8rem"]}
+ shrinkZero
+ />
+ }
+ >
+ </React.Suspense>
+ </Shell>
+ )
+} \ No newline at end of file
diff --git a/app/[lng]/evcp/(evcp)/rfq/[id]/cbe/page.tsx b/app/[lng]/evcp/(evcp)/rfq/[id]/cbe/page.tsx
index bc32641f..fb288a98 100644
--- a/app/[lng]/evcp/(evcp)/rfq/[id]/cbe/page.tsx
+++ b/app/[lng]/evcp/(evcp)/rfq/[id]/cbe/page.tsx
@@ -1,7 +1,9 @@
import { Separator } from "@/components/ui/separator"
import { type SearchParams } from "@/types/table"
import { getValidFilters } from "@/lib/data-table"
-import { searchParamsTBECache } from "@/lib/rfqs/validations"
+import { searchParamsCBECache } from "@/lib/rfqs/validations"
+import { getCBE } from "@/lib/rfqs/service"
+import { CbeTable } from "@/lib/rfqs/cbe-table/cbe-table"
interface IndexPageProps {
// Next.js 13 App Router에서 기본으로 주어지는 객체들
@@ -22,31 +24,31 @@ export default async function RfqCBEPage(props: IndexPageProps) {
// 2) SearchParams 파싱 (Zod)
// - "filters", "page", "perPage", "sort" 등 contact 전용 컬럼
const searchParams = await props.searchParams
- const search = searchParamsTBECache.parse(searchParams)
+ const search = searchParamsCBECache.parse(searchParams)
const validFilters = getValidFilters(search.filters)
- // const promises = Promise.all([
- // getCBE({
- // ...search,
- // filters: validFilters,
- // },
- // idAsNumber)
- // ])
+ const promises = Promise.all([
+ getCBE({
+ ...search,
+ filters: validFilters,
+ },
+ idAsNumber)
+ ])
// 4) 렌더링
return (
<div className="space-y-6">
<div>
<h3 className="text-lg font-medium">
- Technical Bid Evaluation
+ Commercial Bid Evaluation
</h3>
<p className="text-sm text-muted-foreground">
- 초대된 벤더에게 CBE를 보낼 수 있습니다. <br/>"발행하기" 버튼을 통해 CBE를 전송하면 CBE 내용이 메일로 전달되고 eVCP에도 벤더가 입력할 수 있게 자동 생성됩니다.
+ 초대된 협력업체에게 CBE를 보낼 수 있습니다. <br />"발행하기" 버튼을 통해 CBE를 전송하면 CBE 내용이 메일로 전달되고 eVCP에도 협력업체가 입력할 수 있게 자동 생성됩니다.
</p>
</div>
<Separator />
<div>
-
+ <CbeTable promises={promises} rfqId={idAsNumber} />
</div>
</div>
)
diff --git a/app/[lng]/evcp/(evcp)/rfq/[id]/layout.tsx b/app/[lng]/evcp/(evcp)/rfq/[id]/layout.tsx
index 2aac90eb..9a03efa4 100644
--- a/app/[lng]/evcp/(evcp)/rfq/[id]/layout.tsx
+++ b/app/[lng]/evcp/(evcp)/rfq/[id]/layout.tsx
@@ -1,11 +1,12 @@
import { Metadata } from "next"
-
+import Link from "next/link"
import { Separator } from "@/components/ui/separator"
import { SidebarNav } from "@/components/layout/sidebar-nav"
-import { findVendorById } from "@/lib/vendors/service" // 가정: 여기에 findVendorById가 있다고 가정
-import { Rfq, RfqWithItems } from "@/db/schema/rfq"
+import { RfqViewWithItems } from "@/db/schema/rfq"
import { findRfqById } from "@/lib/rfqs/service"
import { formatDate } from "@/lib/utils"
+import { Button } from "@/components/ui/button"
+import { ArrowLeft } from "lucide-react"
export const metadata: Metadata = {
title: "Vendor Detail",
@@ -25,8 +26,8 @@ export default async function RfqLayout({
const id = resolvedParams.id
const idAsNumber = Number(id)
- // 2) DB에서 해당 벤더 정보 조회
- const rfq: RfqWithItems | null = await findRfqById(idAsNumber)
+ // 2) DB에서 해당 협력업체 정보 조회
+ const rfq: RfqViewWithItems | null = await findRfqById(idAsNumber)
// 3) 사이드바 메뉴
const sidebarNavItems = [
@@ -50,11 +51,19 @@ export default async function RfqLayout({
<div className="container py-6">
<section className="overflow-hidden rounded-[0.5rem] border bg-background shadow">
<div className="hidden space-y-6 p-10 pb-16 md:block">
+ <div className="flex items-center justify-end mb-4">
+ <Link href={`/${lng}/evcp/rfq`} passHref>
+ <Button variant="ghost" className="flex items-center text-primary hover:text-primary/80 transition-colors p-0 h-auto">
+ <ArrowLeft className="mr-1 h-4 w-4" />
+ <span>RFQ 목록으로 돌아가기</span>
+ </Button>
+ </Link>
+ </div>
<div className="space-y-0.5">
- {/* 4) 벤더 정보가 있으면 코드 + 이름 + "상세 정보" 표기 */}
+ {/* 4) 협력업체 정보가 있으면 코드 + 이름 + "상세 정보" 표기 */}
<h2 className="text-2xl font-bold tracking-tight">
{rfq
- ? `${rfq.rfqCode ?? ""} 관리`
+ ? `${rfq.projectCode ?? ""} ${rfq.rfqCode ?? ""} 관리`
: "Loading RFQ..."}
</h2>
@@ -63,15 +72,15 @@ export default async function RfqLayout({
? `${rfq.description ?? ""} ${rfq.lines.map(line => line.itemCode).join(", ")}`
: ""}
</p>
- <h3>Due Date:{ rfq && <strong>{formatDate(rfq?.dueDate)}</strong>}</h3>
+ <h3>Due Date:{rfq && rfq?.dueDate && <strong>{formatDate(rfq?.dueDate)}</strong>}</h3>
</div>
<Separator className="my-6" />
<div className="flex flex-col space-y-8 lg:flex-row lg:space-x-12 lg:space-y-0">
- <aside className="-mx-4 lg:w-1/6">
- <SidebarNav items={sidebarNavItems} />
+ <aside className="lg:w-64 flex-shrink-0">
+ <SidebarNav items={sidebarNavItems} />
</aside>
- <div className="flex-1">{children}</div>
- </div>
+ <div className="lg:w-[calc(100%-16rem)] overflow-auto">{children}</div>
+ </div>
</div>
</section>
</div>
diff --git a/app/[lng]/evcp/(evcp)/rfq/[id]/page.tsx b/app/[lng]/evcp/(evcp)/rfq/[id]/page.tsx
index 026ca5ac..1a9f4b18 100644
--- a/app/[lng]/evcp/(evcp)/rfq/[id]/page.tsx
+++ b/app/[lng]/evcp/(evcp)/rfq/[id]/page.tsx
@@ -43,7 +43,7 @@ export default async function RfqPage(props: IndexPageProps) {
Vendors
</h3>
<p className="text-sm text-muted-foreground">
- 등록된 벤더 중에서 이 RFQ 아이템에 매칭되는 업체를 보여줍니다. <br/>"발행하기" 버튼을 통해 RFQ를 전송하면 첨부파일과 함께 RFQ 내용이 메일로 전달되고 eVCP에도 벤더가 입력할 수 있게 자동 생성됩니다.
+ 등록된 협력업체 중에서 이 RFQ 아이템에 매칭되는 업체를 보여줍니다. <br/>"발행하기" 버튼을 통해 RFQ를 전송하면 첨부파일과 함께 RFQ 내용이 메일로 전달되고 eVCP에도 협력업체가 입력할 수 있게 자동 생성됩니다.
</p>
</div>
<Separator />
diff --git a/app/[lng]/evcp/(evcp)/rfq/[id]/tbe/page.tsx b/app/[lng]/evcp/(evcp)/rfq/[id]/tbe/page.tsx
index 15c5d93c..76eea302 100644
--- a/app/[lng]/evcp/(evcp)/rfq/[id]/tbe/page.tsx
+++ b/app/[lng]/evcp/(evcp)/rfq/[id]/tbe/page.tsx
@@ -43,7 +43,7 @@ export default async function RfqTBEPage(props: IndexPageProps) {
Technical Bid Evaluation
</h3>
<p className="text-sm text-muted-foreground">
- 초대된 벤더에게 TBE를 보낼 수 있습니다. <br/>"발행하기" 버튼을 통해 TBE를 전송하면 첨부파일과 함께 TBE 내용이 메일로 전달되고 eVCP에도 벤더가 입력할 수 있게 자동 생성됩니다.
+ 초대된 협력업체에게 TBE를 보낼 수 있습니다. <br/>"발행하기" 버튼을 통해 TBE를 전송하면 첨부파일과 함께 TBE 내용이 메일로 전달되고 eVCP에도 협력업체가 입력할 수 있게 자동 생성됩니다.
</p>
</div>
<Separator />
diff --git a/app/[lng]/evcp/(evcp)/tag-numbering/page.tsx b/app/[lng]/evcp/(evcp)/tag-numbering/page.tsx
index 9d5b903a..44695259 100644
--- a/app/[lng]/evcp/(evcp)/tag-numbering/page.tsx
+++ b/app/[lng]/evcp/(evcp)/tag-numbering/page.tsx
@@ -34,7 +34,7 @@ export default async function IndexPage(props: IndexPageProps) {
<div className="flex items-center justify-between space-y-2">
<div>
<h2 className="text-2xl font-bold tracking-tight">
- Tag Numbering from S-EDP
+ 태그 타입 목록 from S-EDP
</h2>
<p className="text-muted-foreground">
태그 넘버링을 위한 룰셋을 S-EDP로부터 가져오고 확인할 수 있습니다{" "}
diff --git a/app/[lng]/evcp/(evcp)/tasks/page.tsx b/app/[lng]/evcp/(evcp)/tasks/page.tsx
index f14cc757..91b946fb 100644
--- a/app/[lng]/evcp/(evcp)/tasks/page.tsx
+++ b/app/[lng]/evcp/(evcp)/tasks/page.tsx
@@ -38,12 +38,12 @@ export default async function IndexPage(props: IndexPageProps) {
return (
<Shell className="gap-2">
<React.Suspense fallback={<Skeleton className="h-7 w-52" />}>
- {/* <DateRangePicker
+ <DateRangePicker
triggerSize="sm"
triggerClassName="ml-auto w-56 sm:w-60"
align="end"
shallow={false}
- /> */}
+ />
</React.Suspense>
<React.Suspense
fallback={
diff --git a/app/[lng]/evcp/(evcp)/tbe/page.tsx b/app/[lng]/evcp/(evcp)/tbe/page.tsx
index 2461ed42..1a7fdf86 100644
--- a/app/[lng]/evcp/(evcp)/tbe/page.tsx
+++ b/app/[lng]/evcp/(evcp)/tbe/page.tsx
@@ -70,8 +70,8 @@ export default async function RfqTBEPage(props: IndexPageProps) {
Technical Bid Evaluation
</h2>
<p className="text-muted-foreground">
- 초대된 벤더에게 TBE를 보낼 수 있습니다. <br/>
- 체크박스 선택을 하면 초대 버튼이 활성화됩니다. 버튼 클릭 후 첨부파일을 함께 전송하면 TBE 내용이 메일로 전달되고 eVCP에도 벤더가 입력할 수 있게 자동 생성됩니다.
+ 초대된 협력업체에게 TBE를 보낼 수 있습니다. <br/>
+ 체크박스 선택을 하면 초대 버튼이 활성화됩니다. 버튼 클릭 후 첨부파일을 함께 전송하면 TBE 내용이 메일로 전달되고 eVCP에도 협력업체가 입력할 수 있게 자동 생성됩니다.
</p>
</div>
</div>
diff --git a/app/[lng]/evcp/(evcp)/vendor-candidates/page.tsx b/app/[lng]/evcp/(evcp)/vendor-candidates/page.tsx
index 668c0dc6..a6e00b1b 100644
--- a/app/[lng]/evcp/(evcp)/vendor-candidates/page.tsx
+++ b/app/[lng]/evcp/(evcp)/vendor-candidates/page.tsx
@@ -9,6 +9,7 @@ import { Shell } from "@/components/shell"
import { getVendorCandidateCounts, getVendorCandidates } from "@/lib/vendor-candidates/service"
import { searchParamsCandidateCache } from "@/lib/vendor-candidates/validations"
import { VendorCandidateTable } from "@/lib/vendor-candidates/table/candidates-table"
+import { DateRangePicker } from "@/components/date-range-picker"
interface IndexPageProps {
searchParams: Promise<SearchParams>
@@ -30,24 +31,35 @@ export default async function IndexPage(props: IndexPageProps) {
return (
<Shell className="gap-2">
-
+
<div className="flex items-center justify-between space-y-2">
<div className="flex items-center justify-between space-y-2">
<div>
<h2 className="text-2xl font-bold tracking-tight">
- Vendor Candidates Management
- </h2>
+ Vendor Candidates Management
+ </h2>
<p className="text-muted-foreground">
- 수집한 벤더 후보를 등록하고 초대 메일을 송부할 수 있습니다.
-
+ 수집한 협력업체 후보를 등록하고 초대 메일을 송부할 수 있습니다.
</p>
</div>
</div>
</div>
+
+ {/* 수집일 라벨과 DateRangePicker를 함께 배치 */}
+ <div className="flex items-center justify-start gap-2">
+ {/* <span className="text-sm font-medium">수집일 기간 설정: </span> */}
+ <React.Suspense fallback={<Skeleton className="h-7 w-52" />}>
+ <DateRangePicker
+ triggerSize="sm"
+ triggerClassName="w-56 sm:w-60"
+ align="end"
+ shallow={false}
+ showClearButton={true}
+ placeholder="수집일 날짜 범위를 고르세요"
+ />
+ </React.Suspense>
+ </div>
-
- <React.Suspense fallback={<Skeleton className="h-7 w-52" />}>
- </React.Suspense>
<React.Suspense
fallback={
<DataTableSkeleton
@@ -63,4 +75,4 @@ export default async function IndexPage(props: IndexPageProps) {
</React.Suspense>
</Shell>
)
-}
+} \ No newline at end of file
diff --git a/app/[lng]/evcp/(evcp)/vendor-type/page.tsx b/app/[lng]/evcp/(evcp)/vendor-type/page.tsx
new file mode 100644
index 00000000..997c0f82
--- /dev/null
+++ b/app/[lng]/evcp/(evcp)/vendor-type/page.tsx
@@ -0,0 +1,70 @@
+import * as React from "react"
+import { type SearchParams } from "@/types/table"
+
+import { getValidFilters } from "@/lib/data-table"
+import { Skeleton } from "@/components/ui/skeleton"
+import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton"
+import { Shell } from "@/components/shell"
+import { searchParamsCache } from "@/lib/vendor-type/validations"
+import { VendorTypesTable } from "@/lib/vendor-type/table/vendorTypes-table"
+import { getVendorTypes } from "@/lib/vendor-type/service"
+
+
+interface IndexPageProps {
+ searchParams: Promise<SearchParams>
+}
+
+export default async function IndexPage(props: IndexPageProps) {
+ const searchParams = await props.searchParams
+ const search = searchParamsCache.parse(searchParams)
+
+ const validFilters = getValidFilters(search.filters)
+
+ const promises = Promise.all([
+ getVendorTypes({
+ ...search,
+ filters: validFilters,
+ }),
+
+ ])
+
+ return (
+ <Shell className="gap-2">
+ <div className="flex items-center justify-between space-y-2">
+ <div className="flex items-center justify-between space-y-2">
+ <div>
+ <h2 className="text-2xl font-bold tracking-tight">
+ 업체 유형
+ </h2>
+ <p className="text-muted-foreground">
+ 업체 유형을 등록하고 관리할 수 있습니다.{" "}
+
+ </p>
+ </div>
+ </div>
+ </div>
+
+ <React.Suspense fallback={<Skeleton className="h-7 w-52" />}>
+ {/* <DateRangePicker
+ triggerSize="sm"
+ triggerClassName="ml-auto w-56 sm:w-60"
+ align="end"
+ shallow={false}
+ /> */}
+ </React.Suspense>
+ <React.Suspense
+ fallback={
+ <DataTableSkeleton
+ columnCount={6}
+ searchableColumnCount={1}
+ filterableColumnCount={2}
+ cellWidths={["10rem", "40rem", "12rem", "12rem", "8rem", "8rem"]}
+ shrinkZero
+ />
+ }
+ >
+ <VendorTypesTable promises={promises} />
+ </React.Suspense>
+ </Shell>
+ )
+}
diff --git a/app/[lng]/evcp/(evcp)/vendors/[id]/info/layout.tsx b/app/[lng]/evcp/(evcp)/vendors/[id]/info/layout.tsx
index 39e0bac0..4da5af74 100644
--- a/app/[lng]/evcp/(evcp)/vendors/[id]/info/layout.tsx
+++ b/app/[lng]/evcp/(evcp)/vendors/[id]/info/layout.tsx
@@ -23,29 +23,29 @@ export default async function SettingsLayout({
const id = resolvedParams.id
const idAsNumber = Number(id)
- // 2) DB에서 해당 벤더 정보 조회
+ // 2) DB에서 해당 협력업체 정보 조회
const vendor: Vendor | null = await findVendorById(idAsNumber)
// 3) 사이드바 메뉴
const sidebarNavItems = [
{
- title: "Contacts",
+ title: "연락처",
href: `/${lng}/evcp/vendors/${id}/info`,
},
{
- title: "Items",
+ title: "공급품목",
href: `/${lng}/evcp/vendors/${id}/info/items`,
},
{
- title: "RFQ History",
+ title: "견적 히스토리",
href: `/${lng}/evcp/vendors/${id}/info/rfq-history`,
},
{
- title: "Bidding History",
+ title: "입찰 히스토리",
href: `/${lng}/evcp/vendors/${id}/info/bid-history`,
},
{
- title: "Contract History",
+ title: "계약 히스토리",
href: `/${lng}/evcp/vendors/${id}/info/contract-history`,
},
]
@@ -56,13 +56,13 @@ export default async function SettingsLayout({
<section className="overflow-hidden rounded-[0.5rem] border bg-background shadow">
<div className="hidden space-y-6 p-10 pb-16 md:block">
<div className="space-y-0.5">
- {/* 4) 벤더 정보가 있으면 코드 + 이름 + "상세 정보" 표기 */}
+ {/* 4) 협력업체 정보가 있으면 코드 + 이름 + "상세 정보" 표기 */}
<h2 className="text-2xl font-bold tracking-tight">
{vendor
? `${vendor.vendorCode ?? ""} - ${vendor.vendorName} 상세 정보`
: "Loading Vendor..."}
</h2>
- <p className="text-muted-foreground">벤더 관련 상세사항을 확인하세요.</p>
+ <p className="text-muted-foreground">협력업체 관련 상세사항을 확인하세요.</p>
</div>
<Separator className="my-6" />
<div className="flex flex-col space-y-8 lg:flex-row lg:space-x-12 lg:space-y-0">
diff --git a/app/[lng]/evcp/(evcp)/vendors/[id]/info/rfq-history/page.tsx b/app/[lng]/evcp/(evcp)/vendors/[id]/info/rfq-history/page.tsx
index 1d2f618c..c7f8f8b6 100644
--- a/app/[lng]/evcp/(evcp)/vendors/[id]/info/rfq-history/page.tsx
+++ b/app/[lng]/evcp/(evcp)/vendors/[id]/info/rfq-history/page.tsx
@@ -43,7 +43,7 @@ export default async function RfqHistoryPage(props: IndexPageProps) {
RFQ History
</h3>
<p className="text-sm text-muted-foreground">
- 벤더의 RFQ 참여 이력을 확인할 수 있습니다.
+ 협력업체의 RFQ 참여 이력을 확인할 수 있습니다.
</p>
</div>
<Separator />
diff --git a/app/[lng]/evcp/(evcp)/vendors/page.tsx b/app/[lng]/evcp/(evcp)/vendors/page.tsx
index e3cc7fdc..52af0709 100644
--- a/app/[lng]/evcp/(evcp)/vendors/page.tsx
+++ b/app/[lng]/evcp/(evcp)/vendors/page.tsx
@@ -37,15 +37,15 @@ export default async function IndexPage(props: IndexPageProps) {
<div className="flex items-center justify-between space-y-2">
<div>
<h2 className="text-2xl font-bold tracking-tight">
- Vendor Information
+ 협력업체 리스트
</h2>
<p className="text-muted-foreground">
- 벤더에 대한 요약 정보를 확인하고{" "}
+ 협력업체에 대한 요약 정보를 확인하고{" "}
<span className="inline-flex items-center whitespace-nowrap">
<Ellipsis className="size-3" />
<span className="ml-1">버튼</span>
</span>
- 을 통해 담당자 연락처, 입찰 이력, 계약 이력, 패키지 내용 등을 확인 할 수 있습니다. <br/>벤더의 상태에 따라 가입을 승인해주거나 PQ 요청을 할 수 있고 검토가 완료된 벤더를 기간계 시스템에 전송하여 벤더 코드를 따올 수 있습니다.
+ 을 통해 담당자 연락처, 입찰 이력, 계약 이력, 패키지 내용 등을 확인 할 수 있습니다. <br/>벤더의 상태에 따라 가입을 승인해주거나 PQ 요청을 할 수 있고 검토가 완료된 벤더를 기간계 시스템에 전송하여 협력업체 코드를 따올 수 있습니다.
</p>
</div>
</div>
diff --git a/app/[lng]/partners/(partners)/basic-contract/page.tsx b/app/[lng]/partners/(partners)/basic-contract/page.tsx
new file mode 100644
index 00000000..e63e6a17
--- /dev/null
+++ b/app/[lng]/partners/(partners)/basic-contract/page.tsx
@@ -0,0 +1,77 @@
+import * as React from "react"
+import { type SearchParams } from "@/types/table"
+import { getValidFilters } from "@/lib/data-table"
+import { Skeleton } from "@/components/ui/skeleton"
+import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton"
+import { Shell } from "@/components/shell"
+import { getBasicContractsByVendorId } from "@/lib/basic-contract/service"
+import { searchParamsCache } from "@/lib/basic-contract/validations"
+import { redirect } from "next/navigation"
+import { BasicContractsVendorTable } from "@/lib/basic-contract/vendor-table/basic-contract-table"
+import { getServerSession } from "next-auth"
+import { authOptions } from "@/app/api/auth/[...nextauth]/route"
+
+interface IndexPageProps {
+ searchParams: Promise<SearchParams>
+}
+
+export default async function IndexPage(props: IndexPageProps) {
+
+
+ const session = await getServerSession(authOptions)
+ const vendorId = session?.user.companyId
+
+ const searchParams = await props.searchParams
+ const search = searchParamsCache.parse(searchParams)
+
+ const validFilters = getValidFilters(search.filters)
+
+ const promises = Promise.all([
+ getBasicContractsByVendorId(
+ {
+ ...search,
+ filters: validFilters,
+ },
+ Number(vendorId)
+ ),
+ ])
+
+ return (
+ <Shell className="gap-2">
+ <div className="flex items-center justify-between space-y-2">
+ <div className="flex items-center justify-between space-y-2">
+ <div>
+ <h2 className="text-2xl font-bold tracking-tight">
+ 기본계약서 서명 요청현황
+ </h2>
+ <p className="text-muted-foreground">
+ 기본계약서를 비롯하여 초기 서명이 필요한 문서의 서명 현황을 확인할 수 있고 서명을 진행할 수 있습니다. {" "}
+ </p>
+ </div>
+ </div>
+ </div>
+
+ <React.Suspense fallback={<Skeleton className="h-7 w-52" />}>
+ {/* <DateRangePicker
+ triggerSize="sm"
+ triggerClassName="ml-auto w-56 sm:w-60"
+ align="end"
+ shallow={false}
+ /> */}
+ </React.Suspense>
+ <React.Suspense
+ fallback={
+ <DataTableSkeleton
+ columnCount={6}
+ searchableColumnCount={1}
+ filterableColumnCount={2}
+ cellWidths={["10rem", "40rem", "12rem", "12rem", "8rem", "8rem"]}
+ shrinkZero
+ />
+ }
+ >
+ <BasicContractsVendorTable promises={promises} />
+ </React.Suspense>
+ </Shell>
+ )
+} \ No newline at end of file
diff --git a/app/[lng]/partners/(partners)/cbe/page.tsx b/app/[lng]/partners/(partners)/cbe/page.tsx
new file mode 100644
index 00000000..8d03e5f6
--- /dev/null
+++ b/app/[lng]/partners/(partners)/cbe/page.tsx
@@ -0,0 +1,86 @@
+import { type SearchParams } from "@/types/table"
+import { getValidFilters } from "@/lib/data-table"
+import { getCBEbyVendorId, } from "@/lib/rfqs/service"
+import { searchParamsCBECache } from "@/lib/rfqs/validations"
+import { getServerSession } from "next-auth"
+import { authOptions } from "@/app/api/auth/[...nextauth]/route"
+import { TbeVendorTable } from "@/lib/vendor-rfq-response/vendor-tbe-table/tbe-table"
+import * as React from "react"
+import { Skeleton } from "@/components/ui/skeleton"
+import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton"
+import { Shell } from "@/components/shell"
+import { CbeVendorTable } from "@/lib/vendor-rfq-response/vendor-cbe-table/cbe-table"
+
+interface IndexPageProps {
+ // Next.js 13 App Router에서 기본으로 주어지는 객체들
+ params: {
+ lng: string
+ id: string
+ }
+ searchParams: Promise<SearchParams>
+}
+
+export default async function CBEPage(props: IndexPageProps) {
+ const resolvedParams = await props.params
+ const lng = resolvedParams.lng
+
+ // 2) SearchParams 파싱 (Zod)
+ // - "filters", "page", "perPage", "sort" 등 contact 전용 컬럼
+ const searchParams = await props.searchParams
+ const search = searchParamsCBECache.parse(searchParams)
+ const validFilters = getValidFilters(search.filters)
+
+ const session = await getServerSession(authOptions)
+ const vendorId = session?.user.companyId
+ // const vendorId = "17"
+
+ const idAsNumber = Number(vendorId)
+
+ const promises = Promise.all([
+ getCBEbyVendorId({
+ ...search,
+ filters: validFilters,
+ },
+ idAsNumber)
+ ])
+
+
+ return (
+ <Shell className="gap-2">
+ <div className="flex items-center justify-between space-y-2">
+ <div className="flex items-center justify-between space-y-2">
+ <div>
+ <h2 className="text-2xl font-bold tracking-tight">
+ Commercial Bid Evaluation
+ </h2>
+ <p className="text-sm text-muted-foreground">
+ CBE에 응답하고 커뮤니케이션을 할 수 있습니다.{" "}
+ </p>
+ </div>
+ </div>
+ </div>
+
+ <React.Suspense fallback={<Skeleton className="h-7 w-52" />}>
+ {/* <DateRangePicker
+ triggerSize="sm"
+ triggerClassName="ml-auto w-56 sm:w-60"
+ align="end"
+ shallow={false}
+ /> */}
+ </React.Suspense>
+ <React.Suspense
+ fallback={
+ <DataTableSkeleton
+ columnCount={6}
+ searchableColumnCount={1}
+ filterableColumnCount={2}
+ cellWidths={["10rem", "40rem", "12rem", "12rem", "8rem", "8rem"]}
+ shrinkZero
+ />
+ }
+ >
+ <CbeVendorTable promises={promises} />
+ </React.Suspense>
+ </Shell>
+ )
+}
diff --git a/app/[lng]/partners/(partners)/dashboard/page.tsx b/app/[lng]/partners/(partners)/dashboard/page.tsx
index a1e9f8be..3efaa7c3 100644
--- a/app/[lng]/partners/(partners)/dashboard/page.tsx
+++ b/app/[lng]/partners/(partners)/dashboard/page.tsx
@@ -1,8 +1,47 @@
+import * as React from "react"
+import { Skeleton } from "@/components/ui/skeleton"
+import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton"
+import { Shell } from "@/components/shell"
-export default function Pages() {
- return (
- <>
- test
- </>
- )
- } \ No newline at end of file
+
+
+export default async function IndexPage() {
+
+
+ return (
+ <Shell className="gap-2">
+ <div className="flex items-center justify-between">
+ <div>
+ <h2 className="text-2xl font-bold tracking-tight">
+ Dashboard
+ </h2>
+ <p className="text-muted-foreground">
+ 각종 지표 등을 대시보드로 표현하거나 리포트를 출력할 수 있습니다.
+ </p>
+ </div>
+ </div>
+
+ <React.Suspense fallback={<Skeleton className="h-7 w-52" />}>
+ {/* <DateRangePicker
+ triggerSize="sm"
+ triggerClassName="ml-auto w-56 sm:w-60"
+ align="end"
+ shallow={false}
+ /> */}
+ </React.Suspense>
+
+ <React.Suspense
+ fallback={
+ <DataTableSkeleton
+ columnCount={6}
+ searchableColumnCount={1}
+ filterableColumnCount={2}
+ cellWidths={["10rem", "40rem", "12rem", "12rem", "8rem", "8rem"]}
+ shrinkZero
+ />
+ }
+ >
+ </React.Suspense>
+ </Shell>
+ )
+} \ No newline at end of file
diff --git a/app/[lng]/partners/(partners)/document-list/layout.tsx b/app/[lng]/partners/(partners)/document-list/layout.tsx
index a75cdf7d..0eb9d27b 100644
--- a/app/[lng]/partners/(partners)/document-list/layout.tsx
+++ b/app/[lng]/partners/(partners)/document-list/layout.tsx
@@ -6,6 +6,8 @@ import { getVendorProjectsAndContracts } from "@/lib/vendor-data/services"
import { getVendorDocumentLists } from "@/lib/vendor-document/service"
import VendorDocumentsClient from "@/components/documents/vendor-docs.client"
import VendorDocumentListClient from "@/components/document-lists/vendor-doc-list-client"
+import { authOptions } from "@/app/api/auth/[...nextauth]/route";
+import { getServerSession } from "next-auth";
@@ -15,9 +17,9 @@ export default async function VendorDocuments({
}: {
children: React.ReactNode
}) {
- // const session = await getServerSession(authOptions)
- // const vendorId = session?.user.companyId
- const vendorId = "17"
+ const session = await getServerSession(authOptions)
+ const vendorId = session?.user.companyId
+ // const vendorId = "17"
const idAsNumber = Number(vendorId)
const projects = await getVendorProjectsAndContracts(idAsNumber)
diff --git a/app/[lng]/partners/(partners)/documents/layout.tsx b/app/[lng]/partners/(partners)/documents/layout.tsx
index 3ac0c573..dcc2c271 100644
--- a/app/[lng]/partners/(partners)/documents/layout.tsx
+++ b/app/[lng]/partners/(partners)/documents/layout.tsx
@@ -5,6 +5,8 @@ import DocumentContainer from "@/components/documents/document-container"
import { getVendorProjectsAndContracts } from "@/lib/vendor-data/services"
import { getVendorDocumentLists } from "@/lib/vendor-document/service"
import VendorDocumentsClient from "@/components/documents/vendor-docs.client"
+import { authOptions } from "@/app/api/auth/[...nextauth]/route";
+import { getServerSession } from "next-auth";
@@ -14,9 +16,9 @@ export default async function VendorDocuments({
}: {
children: React.ReactNode
}) {
- // const session = await getServerSession(authOptions)
- // const vendorId = session?.user.companyId
- const vendorId = "17"
+ const session = await getServerSession(authOptions)
+ const vendorId = session?.user.companyId
+ // const vendorId = "17"
const idAsNumber = Number(vendorId)
const projects = await getVendorProjectsAndContracts(idAsNumber)
diff --git a/app/[lng]/partners/(partners)/report/page.tsx b/app/[lng]/partners/(partners)/report/page.tsx
new file mode 100644
index 00000000..1d61dc16
--- /dev/null
+++ b/app/[lng]/partners/(partners)/report/page.tsx
@@ -0,0 +1,17 @@
+// app/invalid-access/page.tsx
+
+export default function InvalidAccessPage() {
+ return (
+ <main style={{ padding: '40px', textAlign: 'center' }}>
+ <h1>부적절한 접근입니다</h1>
+ <p>
+ 협력업체(Vendor)가 EVCP 화면에 접속하거나 <br />
+ SHI 계정이 협력업체 화면에 접속하려고 시도하는 경우입니다.
+ </p>
+ <p>
+ <strong>접근 권한이 없으므로, 다른 화면으로 이동해 주세요.</strong>
+ </p>
+ </main>
+ );
+ }
+ \ No newline at end of file
diff --git a/app/[lng]/partners/(partners)/vendor-data/form/[packageId]/[formId]/page.tsx b/app/[lng]/partners/(partners)/vendor-data/form/[packageId]/[formId]/page.tsx
index 01f5b501..dc8df262 100644
--- a/app/[lng]/partners/(partners)/vendor-data/form/[packageId]/[formId]/page.tsx
+++ b/app/[lng]/partners/(partners)/vendor-data/form/[packageId]/[formId]/page.tsx
@@ -7,32 +7,41 @@ interface IndexPageProps {
packageId: string;
formId: string;
};
+ searchParams?: {
+ mode?: string;
+ };
}
-export default async function FormPage({ params }: IndexPageProps) {
+export default async function FormPage({ params, searchParams }: IndexPageProps) {
// 1) 구조 분해 할당
const resolvedParams = await params;
-
- // 2) 구조 분해 할당
+
+ // 2) searchParams도 await 필요
+ const resolvedSearchParams = await searchParams;
+
+ // 3) 구조 분해 할당
const { lng, packageId, formId: formCode } = resolvedParams;
-
- // 2) 변환
+
+ // URL 쿼리 파라미터에서 mode 가져오기 (await 해서 사용)
+ const mode = resolvedSearchParams?.mode === "ENG" ? "ENG" : "IM"; // 기본값은 IM
+
+ // 4) 변환
const packageIdAsNumber = Number(packageId);
-
- // 3) DB 조회
- const { columns, data } = await getFormData(formCode, packageIdAsNumber);
-
- // 4) formId 및 report temp file 조회
+
+ // 5) DB 조회
+ const { columns, data, projectId } = await getFormData(formCode, packageIdAsNumber);
+
+ // 6) formId 및 report temp file 조회
const { formId } = await getFormId(packageId, formCode);
-
- // 5) 예외 처리
+
+ // 7) 예외 처리
if (!columns) {
return (
<p className="text-red-500">해당 폼의 메타 정보를 불러올 수 없습니다.</p>
);
}
-
- // 5) 렌더링
+
+ // 8) 렌더링
return (
<div className="space-y-6">
<DynamicTable
@@ -41,7 +50,9 @@ export default async function FormPage({ params }: IndexPageProps) {
formId={formId}
columnsJSON={columns}
dataJSON={data}
+ projectId={projectId}
+ mode={mode} // 모드 전달
/>
</div>
);
-}
+} \ No newline at end of file
diff --git a/app/[lng]/partners/(partners)/vendor-data/layout.tsx b/app/[lng]/partners/(partners)/vendor-data/layout.tsx
index a8b51c52..29a720de 100644
--- a/app/[lng]/partners/(partners)/vendor-data/layout.tsx
+++ b/app/[lng]/partners/(partners)/vendor-data/layout.tsx
@@ -4,6 +4,8 @@ import { cookies } from "next/headers"
import { Shell } from "@/components/shell"
import { getVendorProjectsAndContracts } from "@/lib/vendor-data/services"
import { VendorDataContainer } from "@/components/vendor-data/vendor-data-container"
+import { authOptions } from "@/app/api/auth/[...nextauth]/route";
+import { getServerSession } from "next-auth";
// Layout 컴포넌트는 서버 컴포넌트입니다
export default async function VendorDataLayout({
@@ -11,9 +13,9 @@ export default async function VendorDataLayout({
}: {
children: React.ReactNode
}) {
- // const session = await getServerSession(authOptions)
- // const vendorId = session?.user.companyId
- const vendorId = "17"
+ const session = await getServerSession(authOptions)
+ const vendorId = session?.user.companyId
+ // const vendorId = "17"
const idAsNumber = Number(vendorId)
// 프로젝트 데이터 가져오기
diff --git a/app/[lng]/partners/(partners)/vendor-data/page.tsx b/app/[lng]/partners/(partners)/vendor-data/page.tsx
index 3eead226..afc3932c 100644
--- a/app/[lng]/partners/(partners)/vendor-data/page.tsx
+++ b/app/[lng]/partners/(partners)/vendor-data/page.tsx
@@ -6,7 +6,7 @@ export default async function IndexPage() {
return (
<div className="space-y-6">
<div>
- <h3 className="text-lg font-medium">벤더 데이터 대시보드</h3>
+ <h3 className="text-lg font-medium">협력업체 데이터 대시보드</h3>
<p className="text-sm text-muted-foreground">
왼쪽 사이드바에서 패키지를 선택하여 태그를 관리하세요.
</p>
diff --git a/app/[lng]/partners/pq/page.tsx b/app/[lng]/partners/pq/page.tsx
index 08faeebb..71741c6c 100644
--- a/app/[lng]/partners/pq/page.tsx
+++ b/app/[lng]/partners/pq/page.tsx
@@ -14,28 +14,30 @@ export default async function PQInputPage({
}) {
// Opt out of caching for this route
noStore()
-
+
// 세션
const session = await getServerSession(authOptions)
- // 예: 세션에서 vendorId 가져오기
- // const vendorId = session?.user.companyId
- const vendorId = 17 // 임시
+ // 세션에서 vendorId 가져오기
+ const vendorId = session?.user.companyId
+ // const vendorId = 17 // 임시
const idAsNumber = Number(vendorId)
- // 서버에서는 모든 데이터를 가져오고, 프로젝트 필터링은 클라이언트에서 진행
+ // 프로젝트 목록 가져오기
const projectPQs = await getPQProjectsByVendorId(idAsNumber)
- // 두 가지 방법으로 수정할 수 있습니다:
-
- // 방법 1: 먼저 allPQData 데이터를 projectId 없이 가져오기
- const allPQData = await getPQDataByVendorId(idAsNumber, undefined)
+ // searchParams에서 projectId 파싱
+ const projectIdParam = searchParams.projectId
+ const projectId = projectIdParam ? parseInt(projectIdParam, 10) : undefined
- // 방법 2: rawProjectId를 클라이언트로 전달하고, 클라이언트가 필터링을 처리
+ // 현재 선택된 프로젝트를 위한 PQ 데이터 가져오기
+ const selectedProjectPQData = projectId
+ ? await getPQDataByVendorId(idAsNumber, projectId)
+ : await getPQDataByVendorId(idAsNumber, undefined)
- // 클라이언트 컴포넌트로 데이터와 원시 searchParams 전달
+ // 클라이언트 컴포넌트로 데이터 전달
return (
<ClientPQWrapper
- allPQData={allPQData}
+ pqData={selectedProjectPQData}
projectPQs={projectPQs}
vendorId={idAsNumber}
rawSearchParams={searchParams}